import { EventDispatcher } from "../../base/event/EventDispatcher";
import { app } from "../../base/typings/global";
import { Injector } from "../../base/utils/Injector";
import { AnalyticsData } from "../publisher/IAnalyticsAble";
import { Task } from "../task/Task";
import { IView, IScene } from "./IView";
import { ViewConfig, IViewLoader } from "./IViewLoader";

/**
 * 注册进入视图时，上报的事件名称，不注册则上报视图配置的name属性
* @param analyticsName 可选参数，默认为view的name，可以指定为上报的中文名称
    * @param group 分组，默认为default，方便分类统计
    * @returns 
    */
export function viewAnalytics(analyticsName?: string, group: string = "default"): any {
    return function (target: { prototype: IView } | string, propertyKey: string) {
        app.viewManager.registerAnalytics(target, analyticsName, group);
    }
}

export interface IViewManager {
    getLoader(config: ViewConfig): IViewLoader;
}
export type ViewAnalyticsInfo = { name: string, group?: string };

type ViewEvent = "open" | "close";

/**
 * 视图管理类
 */
export class ViewManager {
    static KEY: string = "app.ViewManager";
    static ANALYTICS_EVENT_NAME: string = "view";
    private _viewConfigMap: Map<string, ViewConfig> = new Map();
    private _viewAnalyticsMap: Map<string, ViewAnalyticsInfo> = new Map();
    private _scene: IView;
    private _currentView: IView;
    private _currentStack: ViewConfig[] = [];
    private _viewCache: Map<string, IView[]> = new Map();
    private _currentMaskView: IView;
    private _viewEvent: EventDispatcher;
    private static created: boolean = false;
    /**
     * 注册视图上报事件的视图名称，一般为中文方便数据分辨
     * @param viewClass 
     * @param analyticsName 可选参数，默认为view的name，可以指定为上报的中文名称
     * @param group 分组，可选参数，默认为default，方便分类统计
     */
    public registerAnalytics(viewClass: { prototype: IView } | string, analyticsName?: string, group: string = "default"): void {
        let config = this.getViewConfig(viewClass);
        if (config) {
            this._viewAnalyticsMap.set(config.name, { name: analyticsName || config.name, group });
        } else {
            throw new Error(`未获取到视图${viewClass}的配置`);
        }
    }

    /**
     * 注册视图类对应的视图配置
     * @param viewName 视图名称
     * @param config 视图配置
     */
    public registerView(viewName: string, config: ViewConfig): void {
        if (this._viewConfigMap.has(viewName)) {
            throw new Error(`已经存在名称为${viewName}的View`)
        }
        this._viewConfigMap.set(viewName, config);
    }

    /**
     * 获取视图配置
     * @param config 
     * @returns 
     */
    private getViewConfig<T extends ViewConfig>(config: T): T;
    /**
     * 获取视图配置
     * @param veiwName 视图名称
     */
    private getViewConfig<T extends ViewConfig>(veiwName: string): T;
    /**
     * 获取视图配置
     * @param viewClass 视图class
     */
    private getViewConfig<T extends ViewConfig>(viewClass: { prototype: IView }): T;
    private getViewConfig<T extends ViewConfig>(config: { prototype: IView } | T | string): T;
    private getViewConfig<T extends ViewConfig>(config: { prototype: IView } | T | string): T {
        if (typeof config == "string") {
            if (!this._viewConfigMap.has(config)) {
                throw new Error(`${config}视图配置未注册`);
            }
            return <T>this._viewConfigMap.get(config);
        }
        if ("path" in config) {
            return config;
        }
        let values = this._viewConfigMap.values();
        while (true) {
            let item = values.next();
            if (!item || item.done) {
                return null;
            }
            if ((<ViewConfig>item.value)['viewClass'] == config) {
                return item.value;
            }
        }
    }

    constructor() {
        if (ViewManager.created) {
            throw new Error("ViewManager 是单例");
        }
        ViewManager.created = true;
    }

    /**
     * 场景启动后设置场景，不建议手动调用
     * @param root 
     */
    public setRoot(root: IScene) {
        this._viewCache.forEach((views: IView[]) => {
            for (const view of views) {
                view.allDestroy();
            }
        })
        this._viewCache.clear();
        this._currentStack.length = 0;

        this.event("open", root);
        this.reportViewAnalytics("open", root);
        this._scene = root;
        this._currentView = null;
    }

    /**
     * 当前场景
     * @returns 
     */
    public scene<T extends IScene>(): T {
        return this._scene as T;
    }

    /**
     * 开启当前场景的可交互性
     */
    public openTouch(): void {
        if (this._scene) {
            this._scene.touchEnabled = true;
        }
        app.timer.clear(this, this.closeTouch);
    }

    /**
     * 关闭当前场景的可交互性
     * @param maskViewConfig 可选参数，默认为空白，要显示的遮罩试图。
     * @param delay 可选参数，默认200毫秒，显示maskView的延迟时间。
     */
    public async closeTouch(maskViewConfig: { prototype: IView } | ViewConfig | string = null, delay: number = 200) {
        if (!this._scene || !this._scene.touchEnabled) {
            return
        }
        this._scene.touchEnabled = false;
        if (!maskViewConfig || this._currentMaskView) {
            return;
        }
        if (delay <= 0) {
            this._currentMaskView = await this.addView(maskViewConfig, this._scene);
            return;
        }
        app.timer.once(delay, this, this.closeTouch, [maskViewConfig, 0]);
    }

    /**
     * 当前主视图
     * @returns 
     */
    public view<T extends IView>(): T {
        return this._currentView as T;
    }

    /**
     * 切换主视图到目标视图
     * @param target 目标视图的配置
     * @param datas 目标视图初始化参数
     */
    public async switchTo<T extends IView>(config: { prototype: IView } | ViewConfig | string, ...data: any[]): Promise<T> {
        config = this.getViewConfig(config);
        if (!this._scene) {
            throw new Error("根View不存在");
        }
        let view: T = this.getCacheView(config);
        if (view) {
            view = await this._switchView(view, ...data);
        } else {
            view = await this._switchViewByConfig(config, ...data);
        }
        this.cacheView(view);
        this._currentStack.push(config);
        return view;
    }

    /**
     * 切回当前主视图到上一个主视图。
     * @param data 
     * @returns 
     */
    public async switchBack<T extends IView>(...data: any[]): Promise<T> {
        if (this._currentStack.length == 0) {
            console.warn("上一个视图不存在");
            return null;
        }
        //移除当前视图config
        this._currentStack.pop();
        //获取之前视图config
        let lastViewConfig: ViewConfig = this._currentStack.pop();
        if (!lastViewConfig) {
            console.warn("上个主视图为空,");
            return null;
        }
        return await this.switchTo<T>(lastViewConfig, ...data);
    }

    private async _switchView<T extends IView>(view: IView, ...datas: any[]): Promise<T> {
        this._currentView && this._currentView.removeFromParent(!this._currentView.config.cache);
        this._currentView = null;
        this._currentView = view;
        this._scene.addView(this._currentView, view.config.zIndex);
        this._currentView.setData(...datas);
        return this._currentView as T;
    }

    private async _switchViewByConfig<T extends IView>(viewConfig: ViewConfig, ...datas: any[]): Promise<T> {
        this.closeTouch();
        let loader: IViewLoader;
        try {
            loader = await this.loadView(viewConfig);
        } catch (error) {
            console.error(`切换视图错误 code:${error.code}, msg:${error.msg}`);
            throw new Error(`切换视图错误 code:${error.code}, msg:${error.msg}`);
        }
        let name = viewConfig.name || viewConfig.path.substring(viewConfig.path.lastIndexOf("/") + 1);
        this._currentView && this._currentView.removeFromParent(!this._currentView.config.cache);
        this._currentView = null;
        let view: IView = loader.initView(name, true);
        view.name = name;
        view.config = viewConfig;
        view.analyticsInfo = this._viewAnalyticsMap.get(viewConfig.name);
        this._currentView = view;
        this._scene.addView(this._currentView, viewConfig.zIndex);
        app.timer.frameNext(this, () => {
            this.openTouch();
            this._currentView.setData(...datas);
        })
        return this._currentView as T;
    }

    /**
     * 通过视图配置添加视图
     * @param viewConfig 要添加的视图配置或者目标视图
     * @param parent 父级视图，默认为当前主视图，即app.viewMananger.current()
     * @param datas 传入setData的数据
     */
    public async addView<T extends IView>(config: { prototype: IView } | ViewConfig | string, parent?: IView, ...datas: any[]): Promise<T> {
        config = this.getViewConfig(config);
        parent = parent || this._currentView;
        if (!parent) {
            throw new Error("当前View不存在, 且未传入父级视图");
        }
        let view: T = this.getCacheView(config);
        if (view) {
            view = await this._addView(view, parent, ...datas);
        } else {
            view = await this._addViewbyConfig(config, parent, ...datas);
        }
        this.cacheView(view);
        return view as T;
    }

    private async _addView<T extends IView>(view: T, parent: IView, ...datas: any[]): Promise<T> {
        parent.addView(view, view.config.zIndex);
        view.setData(...datas);
        return view;
    }

    private async _addViewbyConfig<T extends IView>(config: ViewConfig, parent: IView, ...datas: any[]): Promise<T> {
        this.closeTouch();
        let loader: IViewLoader;
        try {
            loader = await this.loadView(config);
        } catch (error) {
            console.error(`添加视图错误 code:${error.code}, msg:${error.msg}`);
            throw new Error(`添加视图错误 code:${error.code}, msg:${error.msg}`);
        }
        let name = config.name || config.path.substring(config.path.lastIndexOf("/") + 1);
        let view: T = loader.initView(name, true);
        view.name = name;
        view.config = config;
        view.analyticsInfo = this._viewAnalyticsMap.get(config.name);
        parent.addView(view, config.zIndex);
        app.timer.frameNext(this, () => {
            this.openTouch();
            view.setData(...datas);
        });
        return view;
    }

    /**
     * 加载视图到内存，之后还需要调用loader.initView接口获取view
     * @param config 要加载的视图配置
     * @param task 任务载体
     */
    public loadView(config: { prototype: IView } | ViewConfig | string, task?: Task): Promise<IViewLoader> {
        return new Promise((resolve, reject) => {
            config = this.getViewConfig(config);
            let loader: IViewLoader = this.impl.getLoader(config).init(config);
            task = task || new Task();
            task.once(Task.Event.COMPLETE, this, () => {
                resolve(loader);
            });
            task.once(Task.Event.ERROR, this, (type: string, task: Task, reason: any) => {
                reject(reason);
            });
            loader.load(task);
        })
    }

    /**
     * 获取缓存视图
     * @param config 
     * @returns 
     */
    public getCacheView<T extends IView>(config: { prototype: IView } | ViewConfig | string): T | null {
        config = this.getViewConfig(config);
        let views: IView[] = this._viewCache.get(config.path);
        if (!views || views.length == 0) {
            return null;
        }
        for (const view of views) {
            if (!view.inStage) {
                return view as T;
            }
        }
        return null;
    }

    /**
     * 缓存视图
     * @param view 
     * @param force 默认为false，不考虑view.config.cache的值，强制增加到缓存 
     * @returns 
     */
    public cacheView(view: IView, force: boolean = false): void {
        if (!view.config.cache && !force) {
            return;
        }
        let views: IView[] = this._viewCache.get(view.config.path);
        if (!views) {
            views = [view];
            this._viewCache.set(view.config.path, views);
        } else if (!views.includes(view)) {
            views.push(view);
        }
    }

    private _viewOpenEventMap: Map<(...args: any[]) => void, { event: ViewEvent, viewConfig: ViewConfig, caller: any, listener: (...args: any[]) => void, once: boolean, args: any[] }>;
    private _viewCloseEventMap: Map<(...args: any[]) => void, { event: ViewEvent, viewConfig: ViewConfig, caller: any, listener: (...args: any[]) => void, once: boolean, args: any[] }>;

    /**
     * [快捷方式]增加视图打开的事件监听
     * @param targetView 目标视图或视图名称
     * @param listener 响应回调
     * @param caller 回调this
     * @param once 是否只监听对应视图打开一次。
     * @param args 其他参数
     */
    public onViewOpen(targetView: { prototype: IView } | string, caller: any, listener: (...args: any[]) => void, once: boolean = false, ...args: any[]) {
        this._viewOpenEventMap = this._viewOpenEventMap || new Map();
        this._viewOpenEventMap.set(listener, { event: "open", viewConfig: this.getViewConfig(targetView), caller, listener, once, args });
        this.offViewEvent("open", this, this._onViewOpen);
        this.onViewEvent("open", this, this._onViewOpen);
    }

    protected _onViewOpen(viewName: string, ...args: any[]): void {
        this._viewOpenEventMap.forEach((value, key) => {
            if (value.viewConfig.name == viewName) {
                value.listener.apply(value.caller, value.args);
                value.once && this._viewOpenEventMap.delete(key);
            }
        });
    }

    /**
     * [快捷方式]移除视图打开的事件监听
     * @param listener 
     * @returns 
     */
    public offViewOpen(listener: (...args: any[]) => void): boolean {
        return this._viewOpenEventMap && this._viewOpenEventMap.delete(listener);
    }

    /**
     * [快捷方式]增加视图关闭的事件监听
     * @param targetView 目标视图或视图名称
     * @param listener 响应回调
     * @param caller 回调this
     * @param once 是否只监听对应视图关闭一次。
     * @param args 其他参数
     */
    public onViewClose(targetView: { prototype: IView } | string, caller: any, listener: (...args: any[]) => void, once: boolean = false, ...args: any[]) {
        this._viewCloseEventMap = this._viewCloseEventMap || new Map();
        this._viewCloseEventMap.set(listener, { event: "close", viewConfig: this.getViewConfig(targetView), caller, listener, once, args });
        this.offViewEvent("close", this, this._onViewClose);
        this.onViewEvent("close", this, this._onViewClose);
    }

    /**
     * [快捷方式]移除视图关闭的事件监听
     * @param listener 
     * @returns 
     */
    public offViewClose(listener: (...args: any[]) => void): boolean {
        return this._viewCloseEventMap && this._viewCloseEventMap.delete(listener);
    }

    protected _onViewClose(viewName: string, ...args: any[]): void {
        this._viewCloseEventMap.forEach((value, key) => {
            if (value.viewConfig.name == viewName) {
                value.listener.apply(value.caller, value.args.concat(args));
                value.once && this._viewCloseEventMap.delete(key);
            }
        });
    }

    /**
     * 增加视图事件监听
     * @param event 视图事件名称
     * @param caller 回调this
     * @param listener 响应回调
     * @param once 是否只监听对应视图关闭一次。
     * @param args 其他参数
     */
    public onViewEvent(event: string, caller: any, listener: (viewName: string, ...args: any[]) => void, once: boolean = false, ...args: any[]): void {
        if (!this._viewEvent) {
            this._viewEvent = new EventDispatcher();
        }
        if (once) {
            this._viewEvent.once(event, caller, listener, args);
        } else {
            this._viewEvent.on(event, caller, listener, args);
        }
    }

    /**
     * 移除视图事件监听
     * @param event 事件类型
     * @param caller 事件侦听函数的执行域。
     * @param listener 事件侦听函数。
     * @returns 
     */
    public offViewEvent(event: string, caller: any, listener: (viewName: string, ...args: any[]) => void): void {
        if (!this._viewEvent) {
            return;
        }
        this._viewEvent.off(event, caller, listener);
    }

    /**
     * 广播视图事件
     * @param event 视图事件
     * @param view 视图
     */
    public event(event: "open" | "close" | string, view: IView, ...args: any[]): void {
        if (this._viewEvent) {
            args.unshift(view.name);
            this._viewEvent.event(event, args);
        }
    }

    /**
     * 上报视图统计数据(open\close\...)
     * @param event 事件
     * @param view 视图
     * @param data 上报附带信息
     */
    public reportViewAnalytics(event: "open" | "close" | string, view: IView, data?: AnalyticsData) {
        let maskViewconfig = this._currentMaskView && this._currentMaskView.config;
        if (maskViewconfig && view.name == maskViewconfig.name) {
            return
        }

        let analyticsInfo: any = this._viewAnalyticsMap.get(view.name);
        if (analyticsInfo) {
            data = data || (view.getAnalyticsDataOnViewEvent && view.getAnalyticsDataOnViewEvent(event)) || {};
            data.action = event;
            data.name = analyticsInfo.name;
            data.group = analyticsInfo.group || "default";
            //open:进入时刻, close:停留时长
            data.stay = app.App.durationOfLaunch - (analyticsInfo.lastOpenTime || 0);
            app.publisher.analytics.report(ViewManager.ANALYTICS_EVENT_NAME, data);
            analyticsInfo.lastOpenTime = event == "open" ? app.App.durationOfLaunch : 0;
        }
    }

    private _impl: IViewManager;
    private get impl(): IViewManager {
        if (this._impl == null) {
            this._impl = Injector.getInject(ViewManager.KEY);
        }
        if (this._impl == null) {
            throw new Error(ViewManager.KEY + "未注入！");
        }
        return this._impl;
    }
}

/**
 * 视图管理器
 */
export var viewManager: ViewManager = new ViewManager();
